<template>
    <div class="w100p h100p relative" :id="id">
        <a-modal
            title="编辑公式"
            width="602px"
            :visible="visible"
            @cancel="$emit('cancel')"
            @ok="handleOk"
        >
            <div class="modal-content" id="statistics">
                <a-mentions
                    class="input-box"
                    @change="changeStr"
                    @select="changeStr"
                    ref="varBox"
                    v-model="currentVar"
                    :prefix="'#'"
                    autoFocus
                    rows="3"
                >
                    <a-mentions-option
                        v-for="(value, index) in varArr"
                        :key="index"
                        :value="value.name"
                    >
                        {{ value.name }}
                    </a-mentions-option>
                </a-mentions>

                <div class="show-box">
                    <div class="show-title c-303133 fs-14 fw-400">
                        计算公式=
                    </div>
                    <div class="" v-html="statisticsHtml"></div>
                </div>

                <div class="keyborad-box mt-12">
                    <div class="keyborad-content">
                        <div class="flex">
                            <div class="c-909399 fs-12 fw-400 left-box mt-8">
                                运算变量
                            </div>

                            <div class="flex-1 flex-wrap">
                                <div
                                    class="var-item mr-8 mb-8 pointer"
                                    v-for="(item, index) in varArr"
                                    @click="clickVar(item)"
                                    :key="index"
                                >
                                    {{ item.name }}
                                </div>
                            </div>
                        </div>

                        <div class="flex mt-16">
                            <div class="c-909399 fs-12 fw-400 left-box mt-8">
                                运算符号
                            </div>

                            <div class="flex-1 flex-wrap">
                                <div
                                    class="symbol-item fw-500  flex-c mr-8 mb-8 pointer"
                                    v-for="(item, index) in symbol"
                                    :key="index"
                                    @click="setInputValue(item)"
                                >
                                    {{ item }}
                                </div>
                            </div>
                        </div>

                        <div class="flex mt-16">
                            <div class="c-909399 fs-12 fw-400 left-box mt-8">
                                运算符号
                            </div>

                            <div class="flex-wrap number-box">
                                <div
                                    class="symbol-item fw-500 flex-c mr-8 mb-8 pointer"
                                    v-for="(item, index) in numberArr"
                                    :key="index"
                                    @click="setInputValue(item)"
                                >
                                    {{ item }}
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="keyborad-hint">
                        <div class="fs-14 c-606266 fw-500">使用说明</div>
                        <div class="fs-12 c-606266 fw-400 mt-5">
                            引用变量、运算符等来组成公式进行自动运算。
                        </div>

                        <div class="fs-14 c-606266 fw-500 mt-16">举例</div>
                        <div class="fs-12 c-606266 fw-400 mt-5">计算公式=</div>
                        <div class="mt-5">
                            <span class="c-2979FF bg-D8E6FF radius-4 fs-14"
                            >变量一</span
                            >
                            <span class="c-F65F54">+</span>
                            <span class="c-2979FF bg-D8E6FF radius-4 fs-14"
                            >变量二</span
                            >
                        </div>

                        <div class="fs-12 c-606266 fw-400 mt-8">
                            可以输入[
                            <span class="c-2979FF">#+</span>
                            <span class="c-303133">变量名称</span>
                            ] 快速找到运算变量
                        </div>
                    </div>
                </div>
            </div>
        </a-modal>
    </div>
</template>

<script>
import global from "@/assets/js/global";
export default {
    name: "statistics",
    props: {
        source: {
            // 变量值,元素为对象,示例:{name:"变量名称",id:"变量ID"}
            type: Array,
            default() {
                return [];
            },
        },
        varStr:{
            // 带大括号的公式字符串
            type: String,
            default() {
                return '';
            },
        }
    },

    mounted() {
        this.cursorPostion();
        this.varArr = this.source;
        this.currentVar = this.varStr;
        this.changeStr();
        this.form.valueHtml = this.statisticsHtml
        this.$nextTick(() => {
            this.textareaDom = this.$refs.varBox.$el.children[0];
        });
    },
    data() {
        return {
            form: {  // 整个form对象,会在点击确定的时候$emit返回
                values: "", // 公式字符串
                valueHtml: "", // 公式的HTML
                valueIdHtml: "", // 公式的ID的HTML
                idValueStr:"", // 将变量变成ID之后的字符串
                idList:[], // 选择了的变量的ID数组
            },
            id: global.getRandomId(),
            visible: true,
            operationReg: /[\+\-\*//()]/, // 运算符正则
            numberReg: /\d+(\.\d+)?/, // 数字正则,不限制小数点
            varReg: /^\{[^\}]*\}/, // 变量的正则
            strArr: [],
            typeOrder: [], // 记录字符类型的顺序
            statisticsHtml: "",
            varArr: [
                { label: "列1", value: "1" },
                { label: "列2", value: "2" },
                { label: "列3", value: "3" },
            ], // 变量的数组
            symbol: ["+", "-", "x", "÷", "(", ")"], // 符号
            numberArr: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "."],
            currentVar: "",
            cursorPositionObj: "", // 光标对象
            textareaDom: "", // 输入框的原生dom,用来点击键盘插值使用
            oldStr: "", // 旧的输入框文本,用来比较是否删除了字符
        };
    },
    methods: {
        deleteStr(strLength) {
            /**
            1,获取当前光标的位置加上删除的字符串的长度,得知被删除的字符的下标
            2,拿正则匹配当前所有的变量,可获取下标的index,加上匹配到的字符长度,就是该变量整体的下标跨度
            3,被删除的字符的起点跟终点位置与变量所在的位置下标比较,得出被删除的字符中所包含的变量的位置进行比较,取出最小的起点跟最大的终点,然后切割掉即可.
            
            **/
            // let str = this.oldStr.substring(this.cursorPositionObj.get(this.textareaDom).end,this.cursorPositionObj.get(this.textareaDom).end + strLength) ;
            let deleteStart = this.cursorPositionObj.get(this.textareaDom)
                .start;
            let deleteEnd = deleteStart + strLength;

            let endIndex,
                startIndex = false;

            // let patt = new RegExp(/<\S*>/g,"g");
            let patt = new RegExp(/\{[^\}]*\}/g, "g");

            let result = "";

            while ((result = patt.exec(this.oldStr)) != null) {
                let resultEndIndex = result.index + result[0].length;
                if (
                    (result.index <= deleteStart &&
                        resultEndIndex > deleteStart) ||
                    (result.index >= deleteStart && result.index < deleteEnd)
                ) {
                    endIndex = resultEndIndex;
                    startIndex =
                        startIndex < result.index && startIndex !== false
                            ? startIndex
                            : result.index;
                }
            }

            if (startIndex !== false) {
                this.currentVar =
                    this.oldStr.substr(0, startIndex < deleteStart ? startIndex : deleteStart) +
                    this.oldStr.substr(deleteEnd > endIndex ? deleteEnd : endIndex);

                setTimeout(() => {
                    // 因为vue的变量赋值之后,光标的位置会被定为到最后面,所以需要将光标的位置重新定位回来
                    this.setTextareaPoint(startIndex);
                }, 10);
            }
        },
        setTextareaPoint(index) {
            // 设置光标所在位置
            this.textareaDom.setSelectionRange(index, index);
        },
        clickVar(item) {
            this.setInputValue(` #${item.name} `);
            this.changeStr();
            let pointIndex = this.cursorPositionObj.get(this.textareaDom).start;
            setTimeout(() => {
                this.setTextareaPoint(pointIndex);
            }, 10);
        },
        setInputValue(value) {
            // 从输入框的光标处,插入字符
            let cur = this.cursorPositionObj.get(this.textareaDom);
            this.cursorPositionObj.add(this.textareaDom, cur, value);

            this.changeStr();
        },
        getContainer() {
            // 指定父级容器,解决滚动条滚动时,选择框跟着移动的问题
            return document.getElementById(this.id);
        },
        changeValue() {
            this.$emit("changeValue", this.form);
        },
        getValue() {
            // 父组件可以通过 refs.getValue来获取值
            return this.form;
        },
        handleOk() {
            // console.log();
            let errorVar = document.getElementById('statistics').getElementsByClassName('error-var') // 看是否有错误的变量
            if(errorVar.length){
                this.$message.error('有变量名称不正确,请修改后再保存')
                return;
            }
            // 判断公式是否合理
            let res = this.currentVar.replace(/\{[^\}]*\}/g, '5 ');

            // 虚拟键盘中的乘号与除号需要替换成为真正的运算符
            res = res.replace(/x/g, "*");
            res = res.replace(/÷/g, "/");

            try {

                eval("res = " + res);
                //
                // this.form.values = this.currentVar;
                this.currentVar = this.currentVar.replace(/x/g, "*");
                this.currentVar = this.currentVar.replace(/÷/g, "/");
                this.form.idValueStr = this.form.idValueStr.replace(/x/g, "*");
                this.form.idValueStr = this.form.idValueStr.replace(/÷/g, "/");
                this.form.values = this.currentVar;
                this.form.valueHtml = this.statisticsHtml;
                this.$emit('handleOk',this.form)
                // this.visible = false;
            } catch (err) {
                this.$message.error("公式错误，请检查");
                return;
            }
        },
        changeStr(val) {
            if(typeof val == 'string'){
                let strIndex  = this.cursorPositionObj.get(this.textareaDom)
                    let addStr =  val.substring(strIndex.start-1,strIndex.start)

                    if(addStr == '#'){
                        return
                    }
            }

            if (this.currentVar.length < this.oldStr.length) {
                // 如果是删除了字符,需要判断是否是变量,是的话整个变量全部删除
                this.deleteStr(this.oldStr.length - this.currentVar.length);
            }
            this.currentVar = this.currentVar.replace(/#\S*\s/g, function(
                word
            ) {
                // 如果匹配到完成时空值的时候,则不替换
                // 将变量替换成<>包裹
                if(word.trim() != '#'){
                    return "{" + word.substr(1, word.length - 2) + "}";
                }else{
                    return '#'
                }
            });
            // this.currentVar = this.currentVar.trim()
            this.changeHtml();
            this.oldStr = this.currentVar;
        },
        changeHtml() {
            this.strArr = [];
            this.typeOrder = [];
            this.form.idValueStr = '';
            this.form.idList = [];
            this.form.valueIdHtml = '';
            let strHtml = "";
            let txt = this.currentVar;
            // 虚拟键盘中的乘号与除号需要替换成为真正的运算符
            txt = txt.replace(/x/g, "*");
            txt = txt.replace(/÷/g, "/");
            this.filterStr(txt);
            // 将字符转换成html进行格式化
            this.strArr.map((item, index) => {
                if (this.varReg.test(item)) {
                    this.typeOrder.push("var");
                    let curVar = this.varArr.find(t => `{${t.name}}` == item)
                    if(curVar){
                        // 判断变量是否正确
                        strHtml += `<span class="c-2979FF bg-D8E6FF radius-4 pl-5 pr-5 mr-5">${item}</span>`;
                        this.form.valueIdHtml += `<span class="c-2979FF bg-D8E6FF radius-4 pl-5 pr-5 mr-5">{${curVar.id}}</span>`;
                        this.form.idValueStr += curVar.id;

                        this.form.idList.push(curVar.id);
                    }else{
                        strHtml += `<span class=" radius-4 pl-5 pr-5 mr-5 error-var">${item}</span>`; 
                    }

                
                } else if (this.operationReg.test(item)) {
                    if (item == "-" && this.typeOrder.pop() == "operation") {
                        //  如果当前字符是-,并且上一个字符类型是运算符,则当前的-当做数字处理
                        strHtml += `<span class="c-65B168 mr-5">${item}</span>`;
                        this.form.valueIdHtml += `<span class="c-65B168 mr-5">${item}</span>`
                    } else {

                        strHtml += `<span class="c-F65F54 mr-5">${item}</span>`;
                        this.form.valueIdHtml += `<span class="c-F65F54 mr-5">${item}</span>`
                    }

                    this.form.idValueStr += item
                    this.typeOrder.push("operation");
                } else if (this.numberReg.test(item)) {
                    this.typeOrder.push("number");
                    this.form.idValueStr += item
                    strHtml += `<span class="c-65B168 mr-5">${item}</span>`;
                    this.form.valueIdHtml += `<span class="c-65B168 mr-5">${item}</span>`
                }
            });
            this.statisticsHtml = strHtml;
        },

        filterStr(str) {
            /*
             * 1, 根据正则匹配字符,根据index的大小进行判断当前的字符的类型是什么
             * 2, 然后根据index加上匹配到的字符长度,进行原字符的切割,然后继续匹配,一直匹配到没有字符为止
             * 3, 记录字符类型的出现的顺序,因为如果-号前面是运算符的话,那它将不是运算符,而是负数的数字
             * */
            if (!str) {
                return;
            }
            // 将字符头部的空格字符去掉,否则影响变量的匹配
            str = str.replace(/^\s+/, "");
            let arr = [
                { match: "varReg", index: null },
                { match: "operationReg", index: null },
                { match: "numberReg", index: null },
            ];

            arr.map((item) => {
                item.match = str.match(this[item.match]);
                if (item.match !== null) {
                    item.index = item.match.index;
                }
            });

            // 比较匹配到的正则谁在最前面
            let res = arr.reduce(function(prev, cur) {
                if (cur.index !== null && prev.index !== null) {
                    return cur.index < prev.index ? cur : prev;
                } else {
                    return cur.index !== null ? cur : prev;
                }
            });

            if (res.index !== null) {
                // 变量未匹配完整时,会出错
                this.strArr.push(res.match[0]);
                this.filterStr(str.slice(res.index + res.match[0].length));
            }
        },

        cursorPostion() {
            let _this = this;
            this.cursorPositionObj = {
                get: function(textarea) {
                    // 获取光标位置,传入的参数需要是一个input或者textarea的dom元素
                    var rangeData = { text: "", start: 0, end: 0 };
                    if (textarea.setSelectionRange) {
                        // W3C
                        textarea.focus();
                        rangeData.start = textarea.selectionStart;
                        rangeData.end = textarea.selectionEnd;
                        rangeData.text =
                            rangeData.start != rangeData.end
                                ? textarea.value.substring(
                                      rangeData.start,
                                      rangeData.end
                                  )
                                : "";
                    } else if (document.selection) {
                        // IE
                        textarea.focus();
                        var i,
                            oS = document.selection.createRange(),
                            // Don't: oR = textarea.createTextRange()
                            oR = document.body.createTextRange();
                        oR.moveToElementText(textarea);

                        rangeData.text = oS.text;
                        rangeData.bookmark = oS.getBookmark();

                        // object.moveStart(sUnit [, iCount])
                        // Return Value: Integer that returns the number of units moved.
                        for (
                            i = 0;
                            oR.compareEndPoints("StartToStart", oS) < 0 &&
                            oS.moveStart("character", -1) !== 0;
                            i++
                        ) {
                            // Why? You can alert(textarea.value.length)
                            if (textarea.value.charAt(i) == "\r") {
                                i++;
                            }
                        }
                        rangeData.start = i;
                        rangeData.end = rangeData.text.length + rangeData.start;
                    }

                    return rangeData;
                },

                set: function(textarea, rangeData) {
                    var oR, start, end;
                    if (!rangeData) {
                        alert("You must get cursor position first.");
                    }
                    textarea.focus();
                    if (textarea.setSelectionRange) {
                        // W3C
                        textarea.setSelectionRange(
                            rangeData.start,
                            rangeData.end
                        );
                    } else if (textarea.createTextRange) {
                        // IE
                        oR = textarea.createTextRange();

                        // Fixbug : ues moveToBookmark()
                        // In IE, if cursor position at the end of textarea, the set function don't work
                        if (textarea.value.length === rangeData.start) {
                            //alert('hello')
                            oR.collapse(false);
                            oR.select();
                        } else {
                            oR.moveToBookmark(rangeData.bookmark);
                            oR.select();
                        }
                    }
                },

                add: function(textarea, rangeData, text) {
                    /*
                     * 1, textarea -- textarea原生dom
                     * 2, rangeData -- 当前光标处,可以通过上面的get方法获取返回值之后,当参数传入即可
                     * 3, text -- 需要追加的文本内容
                     * */
                    var oValue, nValue, oR, sR, nStart, nEnd, st;
                    this.set(textarea, rangeData);
                    if (textarea.setSelectionRange) {
                        // W3C
                        oValue = textarea.value;
                        nValue =
                            oValue.substring(0, rangeData.start) +
                            text +
                            oValue.substring(rangeData.end);
                        nStart = nEnd = rangeData.start + text.length;
                        st = textarea.scrollTop;
                        textarea.value = nValue;
                        _this.currentVar = nValue;
                        // Fixbug:
                        // After textarea.values = nValue, scrollTop value to 0
                        if (textarea.scrollTop != st) {
                            textarea.scrollTop = st;
                        }
                        textarea.setSelectionRange(nStart, nEnd);
                    } else if (textarea.createTextRange) {
                        // IE
                        console.log("IE");
                        sR = document.selection.createRange();
                        sR.text = text;
                        sR.setEndPoint("StartToEnd", sR);
                        sR.select();
                    }
                },
            };
        },
    },
};
</script>

<style scoped lang="less">
.set-box {
    padding: 16px;
    box-sizing: border-box;
}

.modal-content {
    display: flex;
    flex-direction: column;
    align-items: center;
}
.input-box {
    width: 470px;
    height: 72px;
    //border-radius: 4px;
    //border: 1px solid #D9D9D9;
    //padding:12px;
}
.show-box {
    padding: 12px;
    border-radius: 4px;
    width: 470px;
    height: 106px;
    border: 1px solid #d9d9d9;
    margin-top: 8px;
    display: flex;
    .show-title {
        width: 80px;
    }
}

.keyborad-box {
    height: 300px;
    width: 470px;
    overflow-y: auto;
    display: flex;
    justify-content: space-between;
    .keyborad-content {
        width: 310px;
        .left-box {
            width: 64px;
        }
        .var-item {
            background: #ffffff;
            border-radius: 4px;
            border: 1px solid #dddfe6;
            font-weight: 400;
            color: #303133;
            padding: 6px 8px;
        }
        .symbol-item {
            width: 32px;
            height: 32px;
            background: #ffffff;
            border-radius: 4px;
            border: 1px solid #dddfe6;
        }
        .number-box {
            width: 162px;
        }
    }
    .keyborad-hint {
        width: 136px;
        background: #f0f2f5;
        padding: 12px 10px;
    }
}

.html-box {
    height: 32px;
    width: 250px;
    background: #ffffff;
    border-radius: 4px;
    border: 1px solid #d9d9d9;
    padding-top: 4px;
    padding-left:5px;
    //display: flex;
    //align-items: center;
}
</style>
